# Python 趣味编程：第 3 章
#    例題 3.5 多个模块
# --------------------------
# 程序名: 03-blocks.py

from tkinter import *
from dataclasses import dataclass
import time
import random

# 初始状态的设置 
DURATION = 0.002     # 绘图间隔（秒）
BALL_X0 = 400        # 球的初始位置(x)
BALL_Y0 = 120        # 球的初始位置(y)
BALL_D = 10          # 球的大小
BALL_VX0 = 6         # 球的初始速度(x)

PADDLE_X0 = 750      # 推杆的初始位置(x)
PADDLE_Y0 = 80       # 推杆的初始位置(y)
PAD_VY = 4           # 推杆的速度

NUM_BLOCKS = 4       # 砖块的数量
BLOCK_X = 10         # 砖块的位置(x)
BLOCK_Y = 100        # 砖块的位置(y)
BLOCK_W = 40         # 砖块的宽
BLOCK_H = 120        # 砖块的高

# 准备改变的颜色
COLORS = ["blue", "red", "green", "yellow", "brown", "gray"]

# -------------------------
@dataclass
class Ball:
    id: int
    x: int
    y: int
    vx: int
    d: int
    c: str

@dataclass
class Paddle:
    id: int
    x: int
    y: int
    w: int
    h: int
    vy: int
    c: str

@dataclass
class Block:
    id: int
    x: int
    y: int
    w: int
    h: int
    c: str
# -------------------------
# ball
# 球的绘制和定义
def make_ball(x, y, vx, d=3, c="black"):
    id = canvas.create_rectangle(x, y, x + d, y + d,
                                 fill=c, outline=c)
    return Ball(id, x, y, vx, d, c)

# 球的移动(左右)
def move_ball(ball):
    ball.x += ball.vx

# 球的重绘
def redraw_ball(ball):
    canvas.coords(ball.id, ball.x, ball.y,
                  ball.x + ball.d, ball.y + ball.d)

# -------------------------
# paddle
# 推杆的绘制和定义
def make_paddle(x, y, w=20, h=100, c="blue"):
    id = canvas.create_rectangle(x, y, x + w, y + h,
                                 fill=c, outline=c)
    return Paddle(id, x, y, w, h, 0, c)

# 推杆的移动(上下)
def move_paddle(pad):
    pad.y += pad.vy

# 改变车轴的颜色
def change_paddle_color(pad, c="red"):
    canvas.itemconfigure(pad.id, fill=c)
    canvas.itemconfigure(pad.id, outline=c)
    redraw_paddle(pad)

# 推杆的重绘
def redraw_paddle(pad):
    canvas.coords(pad.id, pad.x, pad.y,
                  pad.x + pad.w, pad.y + pad.h)

# -------------------------
# block
# 砖块的绘制和定义
def make_block(x, y, w=40, h=120, c="green"):
    id = canvas.create_rectangle(x, y, x + w, y + h,
                                 fill=c, outline=c)
    return Block(id, x, y, w, h, c)

# 消除砖块
def delete_block(block):
    canvas.delete(block.id)

# 生成多个砖块
def make_blocks(n_rows, x0, y0, w, h, pad=10):
    blocks = []
    for x in range(n_rows):
        blocks.append(make_block(x0, y0, w, h))
        x0 = x0 + w + pad
    return blocks

# -------------------------
# wall
# 墙壁的生成
def make_walls(ox, oy, width, height):
    canvas.create_rectangle(ox, oy, ox + width, oy + height)

# -------------------------
# 推杆操作的事件处理
def up_paddle(event):        # 将速度设定为上(负)
    paddle.vy = -PAD_VY

def down_paddle(event):      # 将速度设定为向下(正)
    paddle.vy = PAD_VY

def stop_paddle(event):      # 将速度设定为零
    paddle.vy = 0

# -------------------------
tk = Tk()
canvas = Canvas(tk, width=800, height=600, bd=0)
canvas.pack()
tk.update()

# ------------------
# 准备绘图道具
# ------------------
make_walls(0, 0, 800, 800)
ball = make_ball(BALL_X0, BALL_Y0, BALL_VX0, BALL_D)
paddle = make_paddle(PADDLE_X0, PADDLE_Y0)
blocks = make_blocks(NUM_BLOCKS, BLOCK_X, BLOCK_Y, BLOCK_W, BLOCK_H)

# 连接事件和事件处理程序
canvas.bind_all('<KeyPress-Up>', up_paddle)
canvas.bind_all('<KeyPress-Down>', down_paddle)
canvas.bind_all('<KeyRelease-Up>', stop_paddle)
canvas.bind_all('<KeyRelease-Down>', stop_paddle)

# -------------------------
# 程序的主循环
while True:
    move_paddle(paddle)       # 推杆的移动
    move_ball(ball)           # 球的移动
    if ball.x + ball.vx <= 0: # 左端的框外:反弹
        ball.vx = -ball.vx
    if ball.x + ball.d >= 800:# 把球往右偏了
        break

    # 球到达了挡板的左侧，球的高度刚好在挡板的宽度之内
    if (ball.x + ball.d >= paddle.x \
        and paddle.y <= ball.y <= paddle.y + paddle.h):
        change_paddle_color(paddle, random.choice(COLORS))#改变颜色
        ball.vx = -ball.vx     # 球的移动方向改变

    for block in blocks:
        # 球的×位置到达砖块，Y位置也在砖块范围内
        if (ball.x <= block.x + block.w \
            and block.y <= ball.y <= block.y + block.h):
            ball.vx = - ball.vx      # 把球弹回去
            delete_block(block)      # 消除砖块
            blocks.remove(block)     # 从block列表中删除这个block
            break
    if blocks == [] : break      # blocks列表清空后结束

    redraw_paddle(paddle)     # 推杆的重绘
    redraw_ball(ball)         # 球的重绘
    tk.update()               # 将绘制反映到画面上
    time.sleep(DURATION)      # sleep，直到下一次绘图之前

